1 /*** 2 * Redistribution and use of this software and associated documentation 3 * ("Software"), with or without modification, are permitted provided 4 * that the following conditions are met: 5 * 6 * 1. Redistributions of source code must retain copyright 7 * statements and notices. Redistributions must also contain a 8 * copy of this document. 9 * 10 * 2. Redistributions in binary form must reproduce the 11 * above copyright notice, this list of conditions and the 12 * following disclaimer in the documentation and/or other 13 * materials provided with the distribution. 14 * 15 * 3. The name "Exolab" must not be used to endorse or promote 16 * products derived from this Software without prior written 17 * permission of Exoffice Technologies. For written permission, 18 * please contact info@exolab.org. 19 * 20 * 4. Products derived from this Software may not be called "Exolab" 21 * nor may "Exolab" appear in their names without prior written 22 * permission of Exoffice Technologies. Exolab is a registered 23 * trademark of Exoffice Technologies. 24 * 25 * 5. Due credit should be given to the Exolab Project 26 * (http://www.exolab.org/). 27 * 28 * THIS SOFTWARE IS PROVIDED BY EXOFFICE TECHNOLOGIES AND CONTRIBUTORS 29 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT 30 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 31 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL 32 * EXOFFICE TECHNOLOGIES OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 33 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 34 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 35 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 37 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 38 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED 39 * OF THE POSSIBILITY OF SUCH DAMAGE. 40 * 41 * Copyright 2004-2006 (C) Exoffice Technologies Inc. All Rights Reserved. 42 * 43 * $Id: SocketManager.java,v 1.1 2006/01/11 13:24:05 tanderson Exp $ 44 */ 45 package org.exolab.jms.net.tunnel; 46 47 import java.io.IOException; 48 import java.net.Socket; 49 import java.rmi.server.ObjID; 50 import java.util.HashMap; 51 import java.util.Map; 52 53 import EDU.oswego.cs.dl.util.concurrent.ClockDaemon; 54 import EDU.oswego.cs.dl.util.concurrent.ThreadFactory; 55 import org.apache.log4j.Logger; 56 57 58 /*** 59 * Manages connections for {@link TunnelServlet}. 60 * 61 * @author <a href="mailto:tma@netspace.net.au">Tim Anderson</a> 62 * @version $Revision: 1.1 $ $Date: 2006/01/11 13:24:05 $ 63 */ 64 class SocketManager { 65 66 /*** 67 * Clock daemon for periodically running the reaper. 68 */ 69 private ClockDaemon _daemon; 70 71 /*** 72 * A map of <code>SocketInfo</code>, keyed on identifier. 73 */ 74 private Map _sockets = new HashMap(); 75 76 /*** 77 * The logger. 78 */ 79 private static final Logger _log = Logger.getLogger(SocketManager.class); 80 81 /*** 82 * Reap thread synchronization helper. 83 */ 84 private final Object _reapLock = new Object(); 85 86 /*** 87 * The maximum period that a connection may be idle before it is reaped, in 88 * milliseconds. If <code>0</code> indicates not to reap connections. 89 */ 90 private long _idlePeriod = 30 * 1000; 91 92 93 /*** 94 * Create a new socket. 95 * 96 * @param host the host to connect to 97 * @param port the port to connect to 98 * @return the identifier of the new endpoint 99 * @throws IOException for any I/O error 100 */ 101 public synchronized String open(String host, int port) throws IOException { 102 Socket socket = new Socket(host, port); 103 String id = new ObjID().toString(); 104 SocketInfo result = new SocketInfo(id, socket); 105 _sockets.put(id, result); 106 startReaper(); 107 return id; 108 } 109 110 /*** 111 * Returns a socket given its identifier. 112 * 113 * @param id the endpoint identifier 114 * @return the endpoint corresponding to <code>id</code> or 115 * <code>null</code> 116 */ 117 public synchronized Socket getSocket(String id) { 118 Socket result = null; 119 synchronized (_reapLock) { 120 SocketInfo info = getSocketInfo(id); 121 if (info != null) { 122 info.setUsed(); 123 result = info.getSocket(); 124 } 125 } 126 return result; 127 } 128 129 /*** 130 * Close a connection given its identifier. 131 * 132 * @param id the connection identifier 133 * @throws IOException for any I/O error 134 */ 135 public synchronized void close(String id) throws IOException { 136 Socket socket = getSocket(id); 137 if (socket != null) { 138 try { 139 socket.close(); 140 } finally { 141 _sockets.remove(id); 142 if (_sockets.isEmpty()) { 143 stopReaper(); 144 } 145 } 146 } 147 } 148 149 /*** 150 * Sets the the maximum period that a connection may be idle before it is 151 * reaped, in seconds. 152 * 153 * @param period the idle period, in seconds 154 */ 155 public void setIdlePeriod(int period) { 156 _idlePeriod = period * 1000; 157 } 158 159 /*** 160 * Returns a {@link SocketInfo} given its identifier. 161 * 162 * @param id the endpoint identifier 163 * @return the connection corresponding to <code>id</code> or 164 * <code>null</code> if none exists 165 */ 166 protected SocketInfo getSocketInfo(String id) { 167 return (SocketInfo) _sockets.get(id); 168 } 169 170 171 /*** 172 * Reap idle connections. 173 */ 174 private void reapIdleConnections() { 175 Map.Entry[] entries; 176 synchronized (this) { 177 entries = (Map.Entry[]) _sockets.entrySet().toArray(new Map.Entry[0]); 178 } 179 synchronized (_reapLock) { 180 for (int i = 0; i < entries.length && !stopReaping(); ++i) { 181 Map.Entry entry = entries[i]; 182 SocketInfo info = (SocketInfo) entry.getValue(); 183 long current = System.currentTimeMillis(); 184 long unused = current - info.getUsed(); 185 if (unused > _idlePeriod) { 186 if (_log.isDebugEnabled()) { 187 _log.debug("Reaping idle connection=" + info.getId()); 188 } 189 try { 190 close(info.getId()); 191 } catch (IOException ignore) { 192 } 193 } 194 } 195 } 196 } 197 198 /*** 199 * Starts the reaper for dead/idle connections, if needed. 200 */ 201 private synchronized void startReaper() { 202 if (_daemon == null) { 203 _daemon = new ClockDaemon(); 204 if (_idlePeriod > 0) { 205 _daemon.setThreadFactory(new ThreadFactory() { 206 public Thread newThread(Runnable command) { 207 Thread thread = new Thread(command, "Reaper"); 208 thread.setDaemon(true); 209 return thread; 210 } 211 }); 212 213 _daemon.executePeriodically(_idlePeriod, new Reaper(), false); 214 } 215 } 216 } 217 218 /*** 219 * Stops the reaper for dead/idle connections, if needed. 220 */ 221 private synchronized void stopReaper() { 222 if (_daemon != null) { 223 _daemon.shutDown(); 224 _daemon = null; 225 } 226 } 227 228 /*** 229 * Helper to determines if a reaper should terminate, by checking the 230 * interrupt status of the current thread. 231 * 232 * @return <code>true</code> if the reaper should terminate 233 */ 234 private boolean stopReaping() { 235 return Thread.currentThread().isInterrupted(); 236 } 237 238 /*** 239 * Helper class for reaping idle connections. 240 */ 241 private class Reaper implements Runnable { 242 243 /*** 244 * Run the reaper. 245 */ 246 public void run() { 247 try { 248 reapIdleConnections(); 249 } catch (Throwable exception) { 250 _log.error(exception, exception); 251 } 252 } 253 } 254 255 } 256